Fix perf regression in Read::read_to_end on short reads due to not checking if the cursor has initialized bytes#158083
Conversation
|
r? @Darksonn rustbot has assigned @Darksonn. Use Why was this reviewer chosen?The reviewer was selected based on:
|
Read::read_to_end on short reads due to not checking if the cursor has initialized bytes
| // Note that we don't track already initialized bytes here, but this is fine | ||
| // because we explicitly limit the read size |
There was a problem hiding this comment.
This comment was added in #150129. Should it be removed?
There was a problem hiding this comment.
Unsure. Are these comments still relevant @a1phyr?
There was a problem hiding this comment.
Well, probably not if you start tracking initialized bytes :)
|
@rustbot author |
|
Reminder, once the PR becomes ready for a review, use |
104baaa to
aebb8e1
Compare
aebb8e1 to
c6406c5
Compare
There was a problem hiding this comment.
I didn't track uninitialized bytes in my previous MR because I thought it would be to complicated to do properly.
For example, if this MR improve some existing cases, it will not really solve the pathological case you sent in your issue for larger sizes (eg around a million): when you initialized N bytes, on the next round you will have N-1 spare initialized bytes left, so you won't be able to use set_init() (or you could initialize the rest manually).
All in all, it was a trade-off between code complexity, properly handling common cases but having suboptimal (but still acceptable) behavior in weird cases.
| } | ||
| }; | ||
|
|
||
| initialized_len = cursor.capacity(); |
There was a problem hiding this comment.
This is true only if read_buf.is_init() (use the boolean below to avoid lifetime issues)
| let mut read_buf: BorrowedBuf<'_, u8> = spare.into(); | ||
|
|
||
| let buf_unfilled_len = read_buf.capacity() - read_buf.len(); | ||
| if initialized_len == buf_unfilled_len { |
There was a problem hiding this comment.
This condition is wrong: you compare the old buffer capacity and the new buffer spare capacity, but the start of the buffer has changed since then. It would be less error prone to track initialized bytes counting from the beginning of the Vec.
There was a problem hiding this comment.
Yeah, please store the full capacity of the vector, including anything already written.
|
@rustbot author |
…ecking if the cursor has initialized bytes
c6406c5 to
c0189d9
Compare
|
@rustbot ready |
|
This is still using the wrong condition. Consider this scenario:
But in this scenario the buffer was resized and not initialized, so this is wrong. It's really important that Instead of storing @rustbot author |
Just to clarify, resizing only occurs potentially in spots where we call |
|
It's not obvious to me that that's the case. Maybe you are right, but I can't easily follow the logic. To me, it would be a lot easier to figure out that the code is correct if you stored the capacity. If the vector was reallocated, then I know that the capacity changed, and therefore I know that we will not call |
|
I just noticed that you added two lines of code to recompute The reason I suggested storing the capacity is that you can delete those two lines of code. I'm worried that, in the future, somebody changes this code to add a third place where the vector is reallocated, while forgetting to also update |
I think it does because it seems like the rust/library/std/src/io/mod.rs Lines 574 to 581 in 36714a9 If I'm understanding it correctly, the The reason why I asked if it was safe to do |
|
I'm sorry it looks like you're right. I misunderstood the However, I don't think the current logic looks ideal. Let's say that we have a buffer of capacity 1000 and
So it seems like that on short reads, we should repeatedly call |
This PR fixes #158008.
In particular, in #150129, it refactored some code within
library/std/io/mod.rsto utilizeBorrowedBuf::is_initinstead of manually checkingread_buf.init_len() == buf_lento see if the read buffer had initialized bytes. However, theBorrowedBufis never marked or set as init within this function, and I think this portion of the code:was removed by mistake. This PR reverts the changes made by #150129, so that we can mark the
BorrowedBuf/read_bufas initialized usingBorrowedBuf::set_initif in a previous iteration the cursor has initialized bytes. This would allowmax_read_sizeto not be marked asusize::maxif the read buffer contains initialized bytes.